package io.renren.modules.file.service.impl;

import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import io.renren.common.utils.FileUtil;
import io.renren.common.utils.R;
import io.renren.common.utils.StringUtil;
import io.renren.common.utils.UuidUtil;
import io.renren.modules.file.dao.FileDao;
import io.renren.modules.file.entity.BigFileEntity;
import io.renren.modules.file.entity.FileChunkEntity;
import io.renren.modules.file.service.FileService;
import io.renren.modules.file.vo.FileVoEntity;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ClassUtils;

import java.io.*;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;


@Service("fileService")
public class FileServiceImpl extends ServiceImpl<FileDao, FileChunkEntity> implements FileService {

    @Override
    public List<FileVoEntity> getListByPath(String basePath,Map<String, Object> params) throws Exception{
        try {
            String curpath = (String)params.get("curpath");
            // 判断根目录是否存在，不存在则创建
            File dir = new File(basePath);
            if (!dir.exists()) {
                dir.mkdir();
            }

            // 判断查找的目录是否存在
            File curdir = new File(basePath + curpath);
            if (!curdir.exists()) {
                throw new Exception("路径不存在！");
            }

            List<FileVoEntity> fList = new ArrayList<>();
            File[] files = curdir.listFiles();
            if (files != null) {
                for (File f : files) {
                    FileVoEntity fileVo = new FileVoEntity();
                    fileVo.setFileName(f.getName());
                    fileVo.setIsDir(f.isDirectory());
                    fileVo.setFileSize(f.isDirectory()? FileUtils.sizeOfDirectory(f):f.length());
                    fileVo.setFileType(f.getName().lastIndexOf(".") == -1? ( f.isDirectory() ? "": "?" ):
                            f.getName().substring(f.getName().lastIndexOf(".")) );
                    String filePath = f.getParent().replace(basePath.substring(0,basePath.length()-1), "").replaceAll("\\\\", "/");
                    fileVo.setFilePath(filePath);
                    fList.add(fileVo);
                }
            }
            return fList;
        } catch (Exception ex) {
            throw ex;
        }

    }

    @Override
    public List<FileVoEntity> getListBySearchVal(String basePath,Map<String, Object> params) throws Exception{
        String searchVal = (String)params.get("searchValue");
        // 判断根目录是否存在，不存在则创建
        File dir = new File(basePath);
        if (!dir.exists()) {
            dir.mkdir();
        }

        List<FileVoEntity> fList = new ArrayList<>();
        this.getFileRecurse(basePath,basePath,fList, searchVal.trim());
        return fList;
    }

    // 递归查找 含有 “关键字” 的所有文件
    public void getFileRecurse(String basePath,String path, List<FileVoEntity> newList,String searchVal) {
        File file = new File(path);
        for (File fItem : file.listFiles()) {
            if (fItem.getName().indexOf(searchVal) != -1) {
                FileVoEntity fileVo = new FileVoEntity();
                fileVo.setFileName(fItem.getName());
                fileVo.setIsDir(fItem.isDirectory());
                fileVo.setFileSize(fItem.isDirectory()? FileUtils.sizeOfDirectory(fItem):fItem.length());
                fileVo.setFileType(fItem.getName().lastIndexOf(".") == -1? ( fItem.isDirectory() ? "": "?" ):
                        fItem.getName().substring(fItem.getName().lastIndexOf(".")) );
                String filePath = fItem.getParent().replace(basePath.substring(0,basePath.length()-1), "").replaceAll("\\\\", "/");
                fileVo.setFilePath(filePath);
                newList.add(fileVo);
            }
            if (fItem.isDirectory()) {
                getFileRecurse(basePath,path  + fItem.getName() + "/", newList,searchVal);
            }
        }
    }



    @Override
    @Transactional(rollbackFor = Exception.class)
    public R fileUploadPost(String basePath,FileChunkEntity chunk) {
        File file = new File(basePath, chunk.getFilename());
        try {
            //第一个块,则新建文件
            if (chunk.getChunkNumber() == 1 && !file.exists()) {
                file.createNewFile();
            }
        } catch (IOException e) {
            return R.error("创建文件失败！");
        }
        try {
            //将块文件写入文件中
            InputStream fos = chunk.getFile().getInputStream();
            RandomAccessFile raf = new RandomAccessFile(file, "rw");
            int len = -1;
            byte[] buffer = new byte[1024];
            raf.seek((chunk.getChunkNumber() - 1) * 1024 * 1024);
            while ((len = fos.read(buffer)) != -1) {
                raf.write(buffer, 0, len);
            }

        } catch (Exception e) {
            if (chunk.getChunkNumber() == 1) {
                file.delete();
            }
            return R.error("文件保存错误！");
        }
        if (chunk.getChunkNumber().equals(chunk.getTotalChunks())) {
            if (!"".equals(chunk.getFilename())) {
                this.setProperty(basePath,chunk.getFilename(), basePath + "/" + chunk.getFilename());
            }
            return R.ok("over");
        } else {
            return R.ok();
        }
    }

    public boolean setProperty(String basePath,String fileName, String filePath) {
        Properties prop = new Properties();
        try (
                FileOutputStream oFile = new FileOutputStream(basePath, true);
        ) {
            prop.setProperty(fileName, filePath);
            prop.store(new OutputStreamWriter(oFile, StandardCharsets.UTF_8), null);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return false;
        }
        return true;
    }


    @Override
    public String chunkUploadByteBuffer(FileChunkEntity param, String filePath) {
        try {
            if (StringUtil.isEmpty(param.getTaskId())) {
                param.setTaskId(UuidUtil.getShortUUID());
            }
            /**
             *
             * 1：创建临时文件，和源文件一个路径
             * 2：如果文件路径不存在重新创建
             */
            String fileName = param.getFile().getOriginalFilename();
            String tempFileName = param.getTaskId() + "_tmp";
            File fileDir = new File(filePath);
            if (!fileDir.exists()) {
                fileDir.mkdirs();
            }
            File tempFile = new File(filePath, tempFileName);
            //第一步
            RandomAccessFile raf = new RandomAccessFile(tempFile, "rw");
            //第二步
            FileChannel fileChannel = raf.getChannel();
            //第三步 计算偏移量
            long position = (param.getChunkNumber() - 1) * param.getChunkSize();
            //第四步
            byte[] fileData = param.getFile().getBytes();
            //第五步
            long end = position + fileData.length - 1;
            fileChannel.position(position);
            fileChannel.write(ByteBuffer.wrap(fileData));
            //使用 fileChannel.map的方式速度更快，但是容易产生IO操作，无建议使用
//        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE,position,fileData.length);
//        //第六步
//        mappedByteBuffer.put(fileData);
            //第七步
//        freedMappedByteBuffer(mappedByteBuffer);
//        Method method = FileChannelImpl.class.getDeclaredMethod("unmap", MappedByteBuffer.class);
//        method.setAccessible(true);
//        method.invoke(FileChannelImpl.class, mappedByteBuffer);
            fileChannel.force(true);
            fileChannel.close();
            raf.close();
            //第八步
            boolean isComplete = checkUploadStatus(param, fileName, filePath);
            if (isComplete) {
                renameFile(tempFile, fileName);
            }
            return param.getTaskId();
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * 文件重命名
     * @param toBeRenamed   将要修改名字的文件
     * @param toFileNewName 新的名字
     * @return
     */
    public void renameFile(File toBeRenamed, String toFileNewName) {
        //检查要重命名的文件是否存在，是否是文件
        if (!toBeRenamed.exists() || toBeRenamed.isDirectory()) {
            System.out.println("文件不存在");
            return;
        }
        String p = toBeRenamed.getParent();
        File newFile = new File(p + File.separatorChar + toFileNewName);
        // 检查上传文件是否已经存在，如果存在则删除原来文件
        if (newFile.exists()) {
            newFile.delete();
        }
        //修改文件名
        toBeRenamed.renameTo(newFile);
    }

    /**
     * 检查文件上传进度
     * @return
     */
    public boolean checkUploadStatus(FileChunkEntity param,String fileName,String filePath) throws IOException {
        File confFile = new File(filePath,fileName+".conf");
        RandomAccessFile confAccessFile = new RandomAccessFile(confFile,"rw");
        //设置文件长度
        confAccessFile.setLength(param.getTotalChunks());
        //设置起始偏移量
        confAccessFile.seek(param.getChunkNumber()-1);
        //将指定的一个字节写入文件中 127，
        confAccessFile.write(Byte.MAX_VALUE);
        byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
        confAccessFile.close();//不关闭会造成无法占用
        //这一段逻辑有点复杂，看的时候思考了好久，创建conf文件文件长度为总分片数，每上传一个分块即向conf文件中写入一个127，那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE 127
        for(int i = 0; i<completeStatusList.length; i++){
            if(completeStatusList[i]!=Byte.MAX_VALUE){
                return false;
            }
        }
        //如果全部文件上传完成，删除conf文件
        confFile.delete();
        return true;
    }
    /**
     * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash，在并发情况下很容易发生
     * 正在释放时另一个线程正开始读取，于是crash就发生了。所以为了系统稳定性释放前一般需要检 查是否还有线程在读或写
     * @param mappedByteBuffer
     */
    public static void freedMappedByteBuffer(final MappedByteBuffer mappedByteBuffer) {
        try {
            if (mappedByteBuffer == null) {
                return;
            }
            mappedByteBuffer.force();
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    try {
                        Method getCleanerMethod = mappedByteBuffer.getClass().getMethod("cleaner", new Class[0]);
                        //可以访问private的权限
                        getCleanerMethod.setAccessible(true);
                        //在具有指定参数的 方法对象上调用此 方法对象表示的底层方法
                        sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod.invoke(mappedByteBuffer,
                                new Object[0]);
                        cleaner.clean();
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println("清理缓存出错!!!" + e.getMessage());
                    }
                    System.out.println("缓存清理完毕!!!");
                    return null;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}